<?php
// @formatter:off
/**
 * @file WCGHolder.abs.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-07-24 01:55:33 +0000 (Wed, 24 Jul 2013) $
 *
 * $Id: WCGHolder.abs.php 99 2013-07-24 01:55:33Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0/includes/holders/WCGHolder.abs.php $
 */
// @formatter:on

/**
 * @class WCGHolder This is the specification for a generic holder. It holds the
 * logic to manage list of object, preferable WCGTableRepresentation objects
 * indexed by name and id.
 *
 * @warning Every object called item should have two basic properties:
 *  - name
 *  - id
 */
abstract class WCGHolder extends WCHolder {
	/**
	 * @var int[] This is the list of groups that has been fully loaded.
	 */
	protected $_fullLoadedGroups = array();
	/**
	 * Constructor is protected because all holders must be singletons.
	 * Therefore, each subclass have to implement a way to create and call
	 * the sole instance. For example, using something like this:
	 * @code
	 * protected static $_Instance;
	 *
	 * public static function Instance() {
	 * 	if(!isset(self::$_Instance)) {
	 * 		$c = __CLASS__;
	 * 		self::$_Instance = new $c();
	 * 	}
	 *
	 * 	return self::$_Instance;
	 * }
	 * @endcode
	 */
	protected function __construct() {
		//
		// Loading global pointers.
		global $wcDatabaseAccess;
		//
		// Loading shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess["prefix"];
		// @}
	}
	/**
	 * Class destructor.
	 */
	public function __destruct() {
		unset($this->_items);
		unset($this->_fullLoadedGroups);
	}
	/**
	 * Prevent users to clone the instance.
	 */
	public function __clone() {
		trigger_error(__CLASS__.": Clone is not allowed.", E_USER_ERROR);
	}
	//
	// Public methods.
	/**
	 * This method allows to create a new object in the represented database
	 * table.
	 *
	 * @param string name Name for the new element.
	 * @param WCGroup $group Group to associate the new element with.
	 * @return WCGTableRepresentation Returns the new object. Otherwise,
	 * returns NULL.
	 */
	public function create($name, WCGroup &$group) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = null;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking if this type of represented objects allow
			// duplicated, if not, checking if it already exists.
			if($this->_PropertiesDupNames || !$this->findItemByName($name, $group)) {
				//
				// Creating a statement to insert a new element in
				// the database.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}$this->_PropertiesTable(\n";
					$query .= "                {$this->_PropertiesPrefix}group, {$this->_PropertiesPrefix}name)\n";
					$query .= "        values (:group, :name)";

					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":group" => $group->id,
					":name" => $name
				);
				//
				// Attempting to create a new element.
				if($stmt->execute($params)) {
					//
					// It it was created it is loaded and
					// assigned to returned.
					$out = $this->item($this->_db->lastInsertId(), $group);
				}
			}
		}
		//
		// Returning newly created object.
		return $out;
	}
	/**
	 * This method allows to access an specific element held in this
	 * container.
	 *
	 * @param int $id ID of the element to retrieve.
	 * @param WCGroup $group Group where the retrieving element belongs.
	 * @param WCGTableRepresentation Returns a representation element. When
	 * the ID doesn't match it returns false.
	 */
	public function item($id, WCGroup $group) {
		//
		// Looking for a matching element.
		if($this->findItem($id, $group)) {
			//
			// Returning the element.
			return $this->_items[$group->id]["byid"][$id];
		} else {
			//
			// At this point, the element doesn't exists, false is
			// returned.
			return false;
		}
	}
	/**
	 * This method allows to access an specific element held in this
	 * container.
	 *
	 * @warning When duplicated names are allowed, this method may return the
	 * first loaded item for name.
	 *
	 * @param string $name Name of the element to retrieve.
	 * @param WCGroup $group Group where the retrieving element belongs.
	 * @param WCGTableRepresentation Returns a representation element. When
	 * the ID doesn't match it returns false.
	 */
	public function itemByName($name, WCGroup &$group) {
		//
		// Looking for a matching element.
		if($this->findItemByName($name, $group)) {
			//
			// Returning the element.
			return $this->_items[$group->id]["byname"][$name];
		} else {
			//
			// At this point, the element doesn't exists, false is
			// returned.
			return false;
		}
	}
	/**
	 * This method allows to get a list of id held for certain group by this
	 * singleton class.
	 *
	 * @param WCGroup $group Group where the retrieving element ids belong.
	 * @return int[] Returns list of element ids.
	 */
	public function itemIds(WCGroup &$group) {
		//
		// Setting a default value to be returned.
		$out = array();
		//
		// Checking if this group is already loaded.
		if(!in_array($group->id, $this->_fullLoadedGroups)) {
			//
			// Creating a statement to load all element ids of a
			// group.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  {$this->_PropertiesPrefix}id as id\n";
				$query .= "from    {$this->_dbprefix}{$this->_PropertiesTable}\n";
				$query .= "where   {$this->_PropertiesPrefix}group = :group\n";
				$query .= "order by {$this->_PropertiesPrefix}name asc\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(":group" => $group->id);
			//
			// Requesting ids.
			if($stmt->execute($params)) {
				//
				// Creating a list of ids.
				foreach($stmt->fetchAll() as $row) {
					$out[] = $row["id"];
				}
			}
		} else {
			//
			// When a group is fully loaded, element ids can be found
			// in a easier way.
			$out = array_keys($this->items($group));
		}
		//
		// Returning a list of ids.
		return $out;
	}
	/**
	 * This method allows to get a list of elements held for certain group by
	 * this singleton class.
	 *
	 * @param WCGroup $group Group where the retrieving elements belong.
	 * @return WCGTableRepresentation[] Returns list of elements.
	 */
	public function items(WCGroup &$group) {
		//
		// Checking if this group is already loaded.
		if(!in_array($group->id, $this->_fullLoadedGroups)) {
			//
			// Checking if ther's an item's structure to hold items
			// from the group.
			if(!isset($this->_items[$group->id])) {
				//
				// Creating an empty structure associated with
				// the group.
				$this->_items[$group->id] = array(
					"byid" => array(),
					"byname" => array()
				);
			}
			/** @fixme instead of creating a new query, this may use 'itemIds()'. */
			//
			// Creating a statement to retrieve all element ids from the group.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  {$this->_PropertiesPrefix}id as id\n";
				$query .= "from    {$this->_dbprefix}{$this->_PropertiesTable}\n";
				$query .= "where   {$this->_PropertiesPrefix}group = :group\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(":group" => $group->id);
			//
			// Retrieving element's ids.
			if($stmt->execute($params)) {
				//
				// Checking every item id. This also loads them
				// if they are not.
				foreach($stmt->fetchAll() as $row) {
					$this->findItem($row["id"], $group);
				}
				//
				// At this point, group is considered to be fully
				// loaded.
				$this->_fullLoadedGroups[] = $group->id;
			}
		}
		//
		// Returning the list of elements.
		return in_array($group->id, $this->_fullLoadedGroups) ? $this->_items[$group->id]["byid"] : array();
	}
	/**
	 * This method resets all cached elements in this singleton.
	 */
	public function resetCache() {
		//
		// Running parents method.
		parent::resetCache();
		//
		// Reseting fully loaded groups list.
		$this->_fullLoadedGroups = array();
	}
	//
	// Protected methods.
	/**
	 * This method allows to add an item to internal lists.
	 *
	 * @param WCGTableRepresentation $item Loaded element to be added.
	 */
	protected function addToCache(&$item) {
		//
		// Creating a shortcut the item group's id.
		$gid = $item->group;
		//
		// Checking if ther's an item's structure to hold items from the
		// group.
		if(!isset($this->_items[$gid])) {
			//
			// Creating an empty structure associated with the group.
			$this->_items[$gid] = array(
				"byid" => array(),
				"byname" => array()
			);
		}
		//
		// Adding element the list by ids.
		$this->_items[$gid]["byid"][$item->id] = $item;
		//
		// Adding element the list by names.
		$this->_items[$gid]["byname"][$item->name] = $item;
	}
	/**
	 * This method looks for an element based on its id. If it's not loaded,
	 * it tries to.
	 *
	 * @param int $id ID to look for.
	 * @param WCGroup &$group Group where the element belongs to.
	 * @return boolean Returns true if the element is loaded.
	 */
	protected function findItem($id, WCGroup &$group) {
		//
		// Setting a default value to be returned.
		$out = false;
		//
		// Checking if the element is already loaded or not.
		if(!isset($this->_items[$group->id]) || !isset($this->_items[$group->id]["byid"][$id])) {
			//
			// At this point, the element is not present.
			//
			// Creating a new object to manage the item.
			$item = new $this->_PropertiesItemClass();
			//
			// Attempting to load it from database.
			@$item->load($id, $group);
			//
			// If it was loaded and it has no issues, it can be added.
			if($item->ok()) {
				//
				// Adding item to internal lists.
				$this->addToCache($item);
				//
				// At this point, the element is present on
				// internal lists.
				$out = true;
			} else {
				//
				// Freeing memory.
				unset($item);
			}
		} else {
			//
			// At this point, the element is present on internal
			// lists.
			$out = true;
		}
		//
		// Returning search result.
		return $out;
	}
	/**
	 * This method looks for an element based on its name. If it's not
	 * loaded, it tries to.
	 *
	 * @param string $name Name to look for.
	 * @param WCGroup &$group Group where the element belongs to.
	 * @return boolean Returns true if the element is loaded.
	 */
	protected function findItemByName($name, WCGroup &$group) {
		//
		// Setting a default value to be returned.
		$out = false;
		//
		// Checking if the element is already loaded or not.
		if(!isset($this->_items[$group->id]) || !isset($this->_items[$group->id]["byname"][$name])) {
			//
			// At this point, the element is not present.
			//
			// Creating a new object to manage the item.
			$item = new $this->_PropertiesItemClass();
			//
			// Attempting to load it from database.
			@$item->loadByName($name, $group);
			//
			// If it was loaded and it has no issues, it can be added.
			if($item->ok()) {
				//
				// Adding item to internal lists.
				$this->addToCache($item);
				//
				// At this point, the element is present on
				// internal lists.
				$out = true;
			} else {
				//
				// Freeing memory.
				unset($item);
			}
		} else {
			//
			// At this point, the element is present on internal
			// lists.
			$out = true;
		}
		//
		// Returning search result.
		return $out;
	}
}
?>